/// Copyright 2012, Alex Averian, Yonan Research
/// Released under BSD Licence

#include "stdafx.h"
#include "../qtypes.h"



#include "../qfile.h"
#include "../qfilemap.h"
using namespace micro::files;


#include "../qtime.h"
using namespace micro::utils;



#include "qtarfile.h"
using namespace micro::qzip;



QTarFile::QTarFile( void )
{
}


QTarFile::~QTarFile( void )
{
}


DWORD QTarFile::GetRoundPosition( DWORD dwPos )
{
    DWORD rest = dwPos % TAR_BLOCKSIZE;

    if ( rest )
        dwPos += TAR_BLOCKSIZE - rest;

    return dwPos;
}

void QTarFile::GetFullpath( const TAR_HEADER & Header, LPSTR pstrPath, char chSep )
{
    // TAR filenames can have all kinds of UNIX prefix formats
    // One goal is to clean out entries such as:
    //  ./  ~/  /root
    // as they don't appear meaningfull on Windows.
    size_t iNameOffset = 0;

    if ( Header.name[0] == '/' )
        iNameOffset = 1;

    if ( Header.name[0] == '.' && Header.name[1] == '/' )
        iNameOffset = 2;

    if ( Header.name[0] == '~' && Header.name[1] == '/' )
        iNameOffset = 2;

    // Concat filename from 'prefix' and 'name' members
    strncpy( pstrPath, Header.prefix, sizeof( Header.prefix ) );
    strncat( pstrPath, Header.name + iNameOffset, sizeof( Header.name ) - iNameOffset );



    // Directories have trailing slash
    size_t cchPath = strlen( pstrPath );

    if ( cchPath > 0 && pstrPath[ cchPath - 1 ] == '/' )
        pstrPath[ cchPath - 1 ] = '\0';

    if ( chSep == '/' )
        return;

    // Did caller request Windows path format?
    for ( LPSTR pstrConvert = pstrPath;  *pstrConvert; pstrConvert++ )
    {
        if ( *pstrConvert == '/' )
            *pstrConvert = chSep;
    }
}

/// din cale extrag doar folderul, daca exista
bool QTarFile::GetFolderPath( LPCTSTR file, LPTSTR pstrPath, TCHAR chSep )
{
    _tcscpy_s( pstrPath, _MAX_PATH,  file ); //, chSep );
//     size_t len = _tcslen( pstrPath );
//
//     if ( pstrPath[len - 1] == L'\\' )
//     {
//         pstrPath[len - 1] = L'\0';
//     }

    LPTSTR pstrSep = _tcsrchr( pstrPath, chSep );

    if ( pstrSep )
    {
        pstrSep[0] = L'\0';
        return true;
    }
    else
    {
        pstrPath[0] = L'\0'; //empty string
        return false;
    }

    return false;
}

///cred ca ar trebui pastrat un sep final
bool QTarFile::GetFileName( LPCTSTR path, LPTSTR file )
{
    LPCTSTR pstrSep = _tcsrchr( path, L'\\' );

    if ( pstrSep )
    {
        _tcscpy_s( file, TAR_MAXNAMELEN, pstrSep + 1 );
        return true;
    }
    else
    {
        _tcscpy_s( file, TAR_MAXNAMELEN, path );
        return true;
    }

    return false;
}



bool QTarFile::AddToList( WIN32_FIND_DATA & wfd  )
{
    // Test folder for duplicate entries
    if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY   )
    {

        for ( int i = 0; i < m_Files.GetSize(); i++ )
        {
            if ( ( m_Files[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) == 0 ) //not directory
                continue;

            if ( _tcsicmp( wfd.cFileName, m_Files[i].cFileName ) == 0 )
                return false;
        }
    }

    // Add it to collection
    m_Files.Add( wfd );
    return true;
}

bool   QTarFile::GetFileData( LPCTSTR pwstrFilename, WIN32_FIND_DATA & wfd )
{
    for ( int i = 0; i < m_Files.GetSize(); i++ )
    {
        if ( _tcsicmp( pwstrFilename, m_Files[i].cFileName ) ) //nenul
            continue;

        wfd  = m_Files[i];
        return  true;
    }

    return false;
}

bool QTarFile::ConvertFileInfo( const TAR_HEADER & Info, WIN32_FIND_DATA & wfd )
{

    // Clear and fill out WIN32_FIND_DATA structure
    ::ZeroMemory( &wfd, sizeof( WIN32_FIND_DATA ) );


    // The filename contains the path and may be postfixed with the
    // path-separator.
    char szName[_MAX_PATH] = { 0 };  //TAR_MAXNAMELEN+1
    ///GetFileName( Info, szName );
    GetFullpath( Info, szName, '\\' );

    wcscpy_s( wfd.cFileName, _countof( wfd.cFileName ), QString( szName ) );
    // File timestamps

    time_t tTime = ( time_t ) GetFromOctalText( Info.mtime, sizeof( Info.mtime ) );

    FILETIME ftFile;
    QTime::TimetToFileTime( tTime, ftFile );

    wfd.ftLastWriteTime = wfd.ftLastAccessTime = wfd.ftCreationTime = ftFile;


    // Rough translation of UNIX chmod to WINDOWS file-attributes
    int iMode = GetFromOctalText( Info.mode, sizeof( Info.mode ) );

    if ( ( iMode & 030000 ) != 0 )
        wfd.dwFileAttributes |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;

    if ( ( iMode & 0400 ) == 0 )
        wfd.dwFileAttributes |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;

    if ( ( iMode & 0400 ) == 0 )
        wfd.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;

    if ( ( iMode & 0200 ) == 0 )
        wfd.dwFileAttributes |= FILE_ATTRIBUTE_READONLY;

    switch ( Info.typeflag )
    {
        case '1':
            wfd.dwFileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;
            break;

        case '2':
            wfd.dwFileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;
            break;

        case '5':
            wfd.dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
            break;
    }

    if ( wfd.dwFileAttributes == 0 )
        wfd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;

    if ( (  wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) == 0 )
        wfd.dwFileAttributes |= FILE_ATTRIBUTE_VIRTUAL;

    // Filesize is available too
    wfd.nFileSizeLow = GetFromOctalText( Info.size, sizeof( Info.size ) );
    return true;
}



/**
 * Return the list of children of a sub-folder.
 */
bool QTarFile::GetFilelist( LPCTSTR prefix, QObArray<WIN32_FIND_DATA> & aList )
{
    int len  = m_Files.GetSize();
    TCHAR szFilePath[_MAX_PATH];

    for ( int i = 0; i < len; i++ )
    {
        bool bo = GetFolderPath( m_Files[i].cFileName, szFilePath, L'\\' );

        if ( _wcsicmp( prefix, szFilePath )  ) //not equal
            continue;

        WIN32_FIND_DATA  wfd = m_Files[i];
        bo = GetFileName( m_Files[i].cFileName, wfd.cFileName );

        if ( _tcslen( wfd.cFileName ) )
            aList.Add( wfd );
    }

    return true;
}


/**
 * Delete a file or folder.
 */
bool QTarFile::DeleteFile( LPCTSTR pwstrFilename )
{

    // OK, this is not a very clever way to handle deletes! On multiple deletes
    // we will absolutely kill performance. But this works for now. We simply
    // copy/move the blocks after the deleted file back in position.


    ///TAR_FILEINFO * pInfo = GetFileInfo(    pwstrFilename );

//     if ( pInfo == nullptr )
//         return false;//( ERROR_FILE_NOT_FOUND );

    return true;
}


//////////////////////////////////////////////////////////////////////////
time_t QTarFile::GetUnixTime()
{
    return time( nullptr );
}


/* integer to string-octal conversion, no NULL */
DWORD  QTarFile::GetFromOctalText( LPCSTR pstr, size_t cchMax )
{
    DWORD dwValue = 0;
    sscanf( pstr, "%o", &dwValue );
    return dwValue;
}

void QTarFile::SetOctalText( LPSTR pstr, size_t cchMax, DWORD dwValue )
{
    //   "*"   inseamna width,  adica primul arg de dupa format este width
    _snprintf( pstr, cchMax, "%0*lo", cchMax - 1, ( unsigned long )dwValue );
    pstr[cchMax - 1] = '\0';

}

void QTarFile::MakeHeader( TAR_HEADER & Header, BOOL bIsNew, LPCSTR pstrFilename, DWORD dwFileSize, DWORD dwAttributes )
{
    // Create a .tar file header.
    // NOTE: We don't really support ustar in this version!

    memset( &Header, 0, sizeof( TAR_HEADER ) );
    bool bIsDir =  ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY )  ;
    strncpy( Header.name, pstrFilename, sizeof( Header.name ) - ( bIsDir ? 1 : 0 ) );

    if ( bIsDir )
        strncat( Header.name, "/", 1 );

    for ( size_t i = 0; i < sizeof( Header.name ); i++ )
        if ( Header.name[i] == '\\' )
            Header.name[i] = '/';

    // Apply default properties for a new file...
    if ( bIsNew )
    {
        // Determine file-type
        Header.typeflag = '0';

        if (  dwAttributes & FILE_ATTRIBUTE_REPARSE_POINT )
            Header.typeflag = '1';

        if ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY )
            Header.typeflag = '5';

        // Set file-size
        SetOctalText( Header.size, sizeof( Header.size ), dwFileSize );

        // Set the user id
        DWORD dwGid = 500;
        DWORD dwUid = 500;
        SetOctalText( Header.gid, sizeof( Header.gid ), dwGid );
        SetOctalText( Header.uid, sizeof( Header.uid ), dwUid );
    }

    // Set the file-mode
    DWORD dwMode = ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY ) == 0 ? 0100664 : 040775;

    if ( ( dwAttributes & FILE_ATTRIBUTE_READONLY ) != 0 )
        dwMode -= 0220;

    if ( ( dwAttributes & FILE_ATTRIBUTE_HIDDEN ) != 0 )
        dwMode -= 0444;

    SetOctalText( Header.mode, sizeof( Header.mode ), dwMode );
    // Stamp filetime with current time
    SetOctalText( Header.mtime, sizeof( Header.mtime ), ( DWORD ) GetUnixTime() );

    // Calculate the new check (sum algorithm)
    DWORD dwChecksum = 0;
    memset( Header.chksum, ' ', sizeof( Header.chksum ) );
    LPBYTE pHeader = ( LPBYTE ) &Header;

    for ( DWORD i = 0; i < sizeof( Header ); i++ )
        dwChecksum += pHeader[i];

    SetOctalText( Header.chksum, sizeof( Header.chksum ) - 1, dwChecksum );
}

//////////////////////////////////////////////////////////////////////////
bool QTarFile::CreateArchive2( LPCTSTR file )
{
    m_szFilename = file;
    return m_file.Open( file, QFile::modeCreate | QFile::modeReadWrite );
}


//recursive
bool QTarFile::AddFolder2( LPCTSTR label, LPCTSTR dir )
{
    QString mdir = dir;

    mdir.AddBackslash();
    mdir += L"*.*";

    int count = 0;

    QFindFile ff;

    for ( bool bRes = ff.FindFile( mdir ); bRes;  bRes = ff.FindNextFile() )
    {
        if ( ff.IsDots() )
            continue;

        if ( ff.IsDirectory() )
        {
            QString path = ff.GetFilePath();
            QString file = ff.GetFileName();

            QString flabel =  QString( label ) + QBACKSLASH + file;
            AddFolder2( flabel, path );
            count++;
        }
        else
        {
            QString path = ff.GetFilePath();
            QString file = ff.GetFileName();

            QString flabel =  QString( label ) + QBACKSLASH + file;
            AddFile2( flabel, path );
            count++;
        }

    }

    if ( count ) return true;

    return AddFolder2Intern( label );
}

//helper
bool QTarFile::AddFolder2Intern( LPCTSTR label )
{
    // We can create a folder by writing an empty file with the correct
    // file attributes. The tar_writefile() will know what to do.
    TAR_HEADER Empty = { 0 };
    return AddFile2Intern( label, ( const LPBYTE ) &Empty, 0, FILE_ATTRIBUTE_DIRECTORY );
}

//full path to file
bool QTarFile::AddFile2( LPCTSTR label, LPCTSTR path )
{
    QFileMap fmap;
    fmap.Open( path );

    if ( fmap.IsOpen() )
    {
        return  AddFile2Intern( label, fmap.GetMemory(), fmap.GetMemSize(), FILE_ATTRIBUTE_NORMAL );
    }

    return false;
}

bool QTarFile::AddFile2Intern( LPCTSTR pwstrFilename, const LPBYTE pbBuffer, DWORD dwFileSize, DWORD dwAttributes )
{
    ASSERT( false == m_file.IsClosed() );

    // So we must create the .tar file header
    TAR_HEADER Empty = { 0 };
    TAR_HEADER Header = { 0 };
    MakeHeader( Header, TRUE, QCharString( pwstrFilename ), dwFileSize, dwAttributes );
    // New files are always appended to the end of the archive.
    // There are 2 empty blocks at the end of the file.

    DWORD dwBytesWritten = 0;
    m_file.Write(  &Header, sizeof( Header ), dwBytesWritten );

    if ( dwBytesWritten != sizeof( Header ) )
        return false;

    // Write the file contents
    dwBytesWritten = 0;
    m_file.Write(   pbBuffer, dwFileSize, dwBytesWritten );

    if ( dwFileSize > 0 && dwBytesWritten != dwFileSize  )
        return false;

    // Make sure the file occupies an entire block size
    if (   dwFileSize % TAR_BLOCKSIZE  != 0 )
    {
        int pad = TAR_BLOCKSIZE - ( dwFileSize % TAR_BLOCKSIZE );
        m_file.Write( &Empty, pad, dwBytesWritten );

        if (  dwBytesWritten != pad  )
            return false;
    }

    return true;
}

/**
 * Close the archive.
 */
bool QTarFile::CloseArchive2()
{
    //write 2 emtpy
    ASSERT( false == m_file.IsClosed() );

    TAR_HEADER Empty = { 0 };
    DWORD dwBytesWritten = 0;
    m_file.Write( &Empty, sizeof( Empty ), dwBytesWritten );
    m_file.Write( &Empty, sizeof( Empty ),  dwBytesWritten );


    m_file.Close();
    return true;
}

//////////////////////////////////////////////////////////////////////////
bool QTarFile::CloseArchive3( )
{
    return true;
}

//open for enum/read/extract
bool QTarFile::OpenArchive3( LPCTSTR path )
{
    try
    {

        m_szFilename = path;
        m_filemap.Open( path );

        if ( !m_filemap.IsOpen() )
            return false;

        LPBYTE data		= m_filemap.GetMemory();
        LONGLONG size	= m_filemap.GetMemSize();


        TAR_HEADER Header, Empty = { 0 };
        ASSERT( sizeof( TAR_HEADER ) == TAR_BLOCKSIZE );

        QMemReader mem( data, size );
        ULONGLONG offset = 0;

        while ( true )
        {
            Header = mem.Get<TAR_HEADER>( offset );

            if ( Header.magic[0] != '\0' && Header.magic[0] != ' ' && Header.magic[0] != 'u' )
            {
                throw ( ERROR_FILE_CORRUPT );
            }

            // The empty block signals the EOF
            if ( memcmp( &Header, &Empty, sizeof( Header ) ) == 0 )
                break;


            WIN32_FIND_DATA File = { 0 };
            ConvertFileInfo( Header, File );
            File.dwReserved1 = offset;

            TCHAR file[_MAX_PATH];
            _tcscpy( file,  File.cFileName );

            WIN32_FIND_DATA Folder = File; //pt a copia timpii, offset
            Folder.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;

            ///nesesar, simplifica op de cautare in subFolder
            for ( LPTSTR pstrSep = _tcsrchr( file, L'\\' ); pstrSep; pstrSep = _tcsrchr( file, L'\\' ) )
            {
                pstrSep[0] = L'\0';
                _tcscpy( Folder.cFileName, file );
                AddToList( Folder );
                pstrSep[0] = L'\0';
            }

            AddToList( File  );
            offset += sizeof( TAR_HEADER );

            // Otherwise this is a file-header preceding the file contents
            DWORD dwFileSize = GetFromOctalText( Header.size, sizeof( Header.size ) );
            DWORD dwSpool = GetRoundPosition( dwFileSize );

            //pozitia noului header
            offset += dwSpool;
        }
    }
    catch ( QError & e )
    {
        e.PrintMessage();
        return false;
    }

    return true;
}

bool QTarFile::ExtractFile( LPCTSTR pwstrFilename, LPCTSTR dest, void * progress )
{
    if ( !m_filemap.IsOpen() )
        return false;

    LPBYTE data		= m_filemap.GetMemory();
    LONGLONG size	= m_filemap.GetMemSize();

    WIN32_FIND_DATA   wfd;
    bool bo = GetFileData(  pwstrFilename , wfd );

    if ( !bo )
        //throw ( ERROR_FILE_NOT_FOUND );
        return false;

    DWORD dwFileSize = wfd.nFileSizeLow;
    DWORD offset =  wfd.dwReserved1 + TAR_BLOCKSIZE;

    QFile fis( dest , QFile::modeCreate | QFile::modeWrite );
    size_t writed = fis.Write( data + offset, dwFileSize );

    if ( writed != dwFileSize )
    {
        //sterge fisierul
        ::DeleteFile( dest );

        throw ERROR_WRITE_FAULT;
    }

    return true;
}

bool QTarFile::ExtractFolder( QString & srcDir, QString & dest, void * progress )
{
    if ( !m_filemap.IsOpen() )
        return false;


    //::SHPathPrepareForWrite(nullptr, nullptr, destPath, SHPPFW_DEFAULT);
    ::CreateDirectory( dest, nullptr );


    QObArray<WIN32_FIND_DATA> list;
    bool bo = GetFilelist( srcDir, list );

    int nr = list.GetSize();

    for ( int i = 0; i < nr; i++ )
    {
        QString  dirFile = srcDir + L"\\" + list[i].cFileName;
        QString  destPath = dest + L"\\" + list[i].cFileName;

        WIN32_FIND_DATA fd = {0};
        bool bo  = GetFileData(   dirFile, fd );

        if ( !bo )
        {
            continue;
        }

        if ( fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
        {
            ExtractFolder(  dirFile,   destPath , nullptr );
            continue;
        }

        bo  = ExtractFile( dirFile, destPath, nullptr );

    }

    return true;

}
//////////////////////////////////////////////////////////////////////////
